import mx.controls.ComboBox;
import timber.ui.control.*;

/**
	Webform class.
	Uses an XML configuration file to set values,
	and validation restraints for a form.
	
	Adds all child movieclips that are a form element or an input text item to elements
	when validating and sending data. If an element does not have an entry
	in the XML config files it's contents are passed normally.
	

	@usage
	First load the config xml. And exmaple looks like:
	<form method="post" action="http://localhost/test.php" error="An error occurred contacting the server.">
		<text name="firstname" validates="notEmpty" error="You must enter a value for First Name." tabindex="1" />
		<text name="lastname" validates="notEmpty" error="You must enter a value for Last Name." tabindex="2" />

		<select name="years">
			<option value="2000">2000</option>
			<option value="2001">2001</option>
			<option value="2002">2002</option>
			<option value="2003">2003</option>
		</select>
	</form>
	
	Currently this uses the MX2004 components to create the select boxes, but any mc that
	impliments the basic interface will do.
	
	validates can be any method of the form object (or of the subclass you are using)
	if you need to pass additional parameters you can by separating them with a comma, i.e.
	between,0,10 would make sure the value is between 0 and 10
	
	in your subclass, you can define:
	private function onSubmit();
	private function onError(err, errtype);
	private function onSuccess();
	
	If on submit returns false, the form will not be sent to the server,
	onError and onSuccess should handle the failure and success of submitting the form
	You can also define additional validation methods in the subclass.
	
	
	@author Chandler McWilliams
	@version 2005-07-06
*/
dynamic class timber.controller.Form extends MovieClip
{
	private var elements;
	private var config;
	private var method;
	private var action;
	private var hidden;
	private var loadErrorMsg;
	private var errs;
	
	public static var VALIDATIONERROR = 1;
	public static var LOADERROR = 2;
	public static var CONFIGERROR = 3;
	
// ===========================================================
// - CONSTRUCTOR
// ===========================================================
	public function Form() {
		var self = this;
		elements = {};
		hidden = {};
		errs = [];
		
		config = new XML();
		config.ignoreWhite = true;
		config.onLoad = function(ok) {
			if (ok) {
				var kids, i, j, opts, sel, item;
				
				// save action and method
				self.method = this.firstChild.attributes.method;
				self.action = this.firstChild.attributes.action;
				self.loadErrorMsg = this.firstChild.attributes.error;
				
				kids = this.firstChild.childNodes;
				for (i=0; i<kids.length; i++) {
					// add each element if as a property, since xpath ain't built in.
					this[kids[i].attributes.name] = {
						type:			kids[i].nodeName,
						validates:	kids[i].attributes.validates,
						error:		kids[i].attributes.error
					};
					
					// if there is a tabindex, set it
					self[kids[i].attributes.name].tabIndex = kids[i].attributes.tabindex;
					
					switch (kids[i].nodeName) {
						// if it's a select item
						// add it's options
						case ('select'):
							// make sure the item exists
							sel = self[kids[i].attributes.name];
							if (sel instanceof ComboBox || sel instanceof ComboBoxWrapper) {
								opts = kids[i].childNodes;
								for (j=0; j<opts.length; j++) {
									item = sel.addItem(opts[j].firstChild.nodeValue, opts[j].attributes.value);
									if (opts[j].attributes.selected == "true") sel.setSelectedIndex(j);
								}
							}
						break;
						
						// override checkbox or radio button value
						// and check if enabled
						// fancy fallthough use here...
						case ('checkbox'):
							if (!self[kids[i].attributes.name] instanceof Checkbox) break;

						case ('radiobutton'):
							if (!self[kids[i].attributes.name] instanceof RadioButton) break;
							
							
							// so far so good, set the stuff
							if (kids[i].attributes.value)
								self[kids[i].attributes.name].setValue(kids[i].attributes.value);

							if (kids[i].attributes.enabled == 'true')
								self[kids[i].attributes.name].check();
							
						break;
						
						case ('hidden'):
							self.hidden[kids[i].attributes.name] = kids[i].attributes.value;
						break;
						
						
					}
				}
			} else {
				self.onError('Failed to load form config xml.', Form.CONFIGERROR);
			}
		}
	}

	public function load(url) {
		config.load(url);
	}

// ===========================================================
// - SEND THE DATA
// ===========================================================
	public function submit() {
		if (!onSubmit()) return;
		
		var prop, me, val, i;
		var vars = {};
		
		// clear errs
		errs = [];
		
		// add and validate elements
		for (prop in this) {
			me = this[prop];
			//trace("From Form, me is (" + me + ").");
			switch (true) {
				// if it's a textfield
				case (me instanceof TextField):
					// look it up in the config
					// see if it needs to be validated
					if (typeof config[me._name].validates != 'undefined') {
						if (validate(me.text, config[me._name].validates)) {
							vars[me._name] = me.text;
						} else {
							addError(me._name, config[me._name].error, config[me._name].validates);
						}
					} else if (config[me._name]) {
						vars[me._name] = me.text;
					}

			
				break;


				case (me instanceof ComboBox):
					// look it up in the config
					// see if it needs to be validated
					if (typeof config[me._name].validates != 'undefined') {
						if (validate(me.value, config[me._name].validates)) {
							if (!vars[me._name]) vars[me._name] = me.value;
						} else {
							addError(me._name, config[me._name].error, config[me._name].validates);
						}
					} else {
						if (!vars[me._name]) vars[me._name] = me.value;
					}
					
				break;

				// if you can getValue and getName, that's good enough for me
				case (me instanceof FormElement):
					trace("me (" + me + ") is a FormElement.");
					// look it up in the config
					// see if it needs to be validated
					if (typeof config[me.getName()].validates != 'undefined') {
						if (validate(me.getValue(), config[me.getName()].validates)) {
							if (!vars[me.getName()]) vars[me.getName()] = me.getValue();
						} else {
							addError(me.getName(), config[me.getName()].error, config[me.getName()].validates);
						}
					} else {
						trace("me (" + me + ") has an entry in vars? (" + vars[me.getName()] + ")");
						if (!vars[me.getName()]) vars[me.getName()] = me.getValue();
					}

						
				break;
			}
		}
		
		// add hidden elements
		for (prop in hidden) {
			vars[prop] = hidden[prop];
		}
		
		// if errs isn't empty, trouble
		if (errs.length) {
			onError(errs, Form.VALIDATIONERROR);
			return false;
		} else {
			sendData(vars);
		}
	}

	public function sendData(info) {
		trace('+-----------------------------------+');
		trace('	Send to ' + action);
		for (var p in info) trace(p + ' :: ' + info[p]);
		trace('+-----------------------------------+');
		trace('');
	
	
		var vars = new LoadVars();
		for (var p in info) vars[p] = info[p];
		
		var response = new LoadVars();
		var self = this;
		response.onLoad = function(ok) {
			if (ok) {
				self.onResponse(this);
			} else {
				self.onResponse(false);
			}
		}
		vars.sendAndLoad(action, response, method);		
	}
	
	// add a form element
	public function addElement(key, value) {
		elements[key] = reference;
	}
	
	// validate a value using validates value from form xml
	private function validate(value, using) {
		if (using.indexOf(',') > -1) {
			var s = using.split(',');
			var method = s.shift();
			return this[method](value, s);
		} else {
			return this[using](value);
		}
	}
	
	
// ===========================================================
// - RESPONSE AND ERROR, SHOULD BE OVERLOADED
// ===========================================================
	// this should be overloaded in subclasses
	private function onResponse(result) {
		if (result === false) {
			onError(loadErrorMsg, Form.LOADERROR);
		} else {
			onSuccess();
		}
	}	
	
	private function addError(name, msg, validates) {
		if (msg == '') msg = name + ' turned was ' + validates;
		errs.push(msg);
	}
	
	// called if there is a error
	// errtype is either:
	// Form.VALIDATIONERROR, Form.CONFIGERROR
	// or
	// Form.LOADERROR
	private function onError(err, errtype) {
		trace('ERRTYPE: ' + errtype);
		if (err.push) {
			trace(err.join('\n'));
		} else {
			trace(err);
		}
	}
		
	// called on success
	private function onSuccess() {
		trace('success');
	}
		
	private function onSubmit() {
		return true;
	}
	
// ===========================================================
// - VALIDATION METHODS
// ===========================================================
	private function notEmpty(val) {
		if (val === false) return false;
		if (val == 'false') return false;
		if (val == '') return false;
		if (typeof val == 'undefined') return false;
		if (val == 0) return false;
		return true;
	}
	
	private function between(val, args) {
		var min = args[0];
		var max = args[1];

		if (val > min && val < max) return true;
		return false;
	}

	private function isEmail(val) {
		return !((val.indexOf("@") == -1) || (val.indexOf(".") == -1) || (val.length<=3));
	}
}